package pl.edu.agh.intobl;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

import pl.edu.agh.intobl.exceptions.IntOblException;

/**
 * Class representing one route in a routeSet - legitimate problem solution
 */
public class Route {

	private LinkedList<Demand> demands = new LinkedList<Demand>();

	public Integer capacity() {
		int capacity = 0;
		for (Demand demand : demands)
			capacity += demand.load;
		
		return capacity;
	}
	
	public boolean addDemander(int demanderNumber, int demanderLoad) {
		if (capacity() + demanderLoad <= ProblemSolver.vehiclesCapacity) {
			demands.add(new Demand(demanderNumber, demanderLoad));
			return true;
		}
		
		return false;
	}
	
	public boolean addDemander(Demand demand) {
		if (capacity() + demand.load <= ProblemSolver.vehiclesCapacity) {
			demands.add(demand);
			return true;
		}
		
		return false;
	}
	
	public int size() {
		return demands.size();
	}

	public Route getSubRoute(int subRouteStartIndex, int subRouteSize) {
		Route subRoute = new Route();
		for (int i=0; i<subRouteSize; i++) {
			Demand demand = demands.get(subRouteStartIndex + i);
			subRoute.addDemander(demand.demander, demand.load);
		}
		
		return subRoute;
	}
	
	public Route removeSubRoute(int subRouteStartIndex, int subRouteSize) {
		Route subRoute = new Route();
		for (int i=0; i<subRouteSize; i++) {
			Demand demand = demands.remove(subRouteStartIndex + i);
			subRoute.addDemander(demand.demander, demand.load);
		}
		
		return subRoute;
	}

	public int findClosestDemandNotBelongingToRoute() {
		List<Integer> demandsNr = new LinkedList<Integer>();
		for (Demand demand : demands)
			demandsNr.add(demand.demander);
		
		Collections.sort(demandsNr);
		
		int closest = 1;
		while (demandsNr.contains(closest))
			closest++;
		int distance = ProblemSolver.distanceMatrix[demands.get(0).demander][closest];
		
		for (int i=1; i<ProblemSolver.distanceMatrix[demands.get(0).demander].length; i++) {
			if (demandsNr.contains(i))
					continue;
			
			if (ProblemSolver.distanceMatrix[demands.get(0).demander][i] < distance) {
				distance = ProblemSolver.distanceMatrix[demands.get(0).demander][i];
				closest = i;
			}
		}
		return closest;
	}

	public Route diff(Route subRoute) {
		Route diffRoute = new Route();
		for (int i=0; i<demands.size(); i++)
			if (!subRoute.contains(demands.get(i).demander))
				diffRoute.addDemander(demands.get(i).demander, demands.get(i).load);
				
		return diffRoute;
	}

	boolean contains(Integer demander) {
		for (Demand demand : demands)
			if (demand.demander == demander)
				return true;
		
		return false;
	}
	
	int indexOfDemander(int demanderNr) {
		for (Demand demand : demands)
			if (demand.demander == demanderNr)
				return demands.indexOf(demand);
		
		return -1;
	}

	public void insertAfter(int demanderNr, Route subRoute) throws IntOblException {
		int index = indexOfDemander(demanderNr);
		if (index == -1)
			throw new IntOblException("Fatal error");//should never happen 
			
		demands.addAll(index + 1, subRoute.demands);
	}
	
	@Override
	public Object clone() {
		Route clonedRoute = new Route();
		for (int i=0; i<demands.size(); i++)
			clonedRoute.addDemander(demands.get(i).demander, demands.get(i).load);
		
		return clonedRoute;
	}

	public void swapDemand(Random rand) {
		int firstIndex = rand.nextInt(demands.size());
		int secondIndex = rand.nextInt(demands.size());
		
		Collections.swap(demands, firstIndex, secondIndex);
	}

	public void inverseRandomSubRoute(Random rand) {
		int subRouteSize = rand.nextInt(demands.size()-1) + 1;
		int index = rand.nextInt(demands.size() - subRouteSize);
		
		Collections.reverse(demands.subList(index, index + subRouteSize));
	}

	public Demand removeRandomDemand(Random rand) {
		return demands.remove(rand.nextInt(demands.size()));
	}

	public void insertAfterRandom(Demand demand, Random rand) {
		demands.add(rand.nextInt(demands.size()), demand);
	}

	public void insertAfterRandom(Route subRoute, Random rand) {
		demands.addAll(rand.nextInt(demands.size()), subRoute.demands);
	}
	
	@Override
	public String toString() {
		return capacity() + ": " + demands.toString();
	}

	public Route cutTail() {
		Route tailRoute = new Route();
		while (capacity() > ProblemSolver.vehiclesCapacity) {
			tailRoute.demands.addFirst(demands.removeLast());
		}
		return tailRoute.size() == 0 ? null : tailRoute;
	}

	public int length() {
		int length = ProblemSolver.distanceMatrix[0][demands.getFirst().demander];
		for (int i=0; i<demands.size()-1; i++)
			length += ProblemSolver.distanceMatrix[demands.get(i).demander][demands.get(i+1).demander];
			
		length += ProblemSolver.distanceMatrix[demands.getLast().demander][0];
		return length;
	}
}
